#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2, re, sys, os

def getModuleDir():
    path = sys.executable if hasattr(sys, 'frozen') else __file__
    return os.path.dirname(os.path.abspath(path))

def userListUrl():
    path = os.path.join(getModuleDir(), 'userlist.ini')
    prefix = 'file:///' if sys.platform=='win32' else 'file://'
    return prefix + path.replace(os.path.sep,'/')

def getProxy():
    proxy = None
    try:
        f = open(os.path.join(getModuleDir(), 'gui.ini'), 'r')
        for line in f:
            line = map(str.strip, line.split('=',1))
            if len(line) == 1: continue
            if line[0].lower()=='proxy' and line[1]:
                proxy = {'http': line[1]}
                break
        f.close()
    except:
        pass
    return proxy

# String constants
try:
    pacFile = sys.argv[1]
except IndexError:
    pacFile = 'proxy.pac'
fetchProxy = getProxy()
rulesUrl = ('http://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt',
            userListUrl())
#print '%s\n%s\n%s\n' % (pacFile, fetchProxy, rulesUrl)
rulesBegin = '// AUTO-GENERATED RULES, DO NOT MODIFY!'
rulesEnd = '// END OF AUTO-GENERATED RULES'

defaultPacTemplate = '''//Proxy Auto Configuration

function FindProxyForURL(url, host) {
    var PROXY = 'PROXY %(proxy)s';
    var DEFAULT = 'DIRECT';
    if (host == 'wallproxy') return PROXY;
    if (inAutoProxy(url, host)) return PROXY;
    return DEFAULT;
}

function dnsDomainIs(host, domain) {
    if (host == domain) return true;
    if (domain.charAt(0) != '.') domain = '.' + domain;
    return (host.length >= domain.length &&
            host.substring(host.length - domain.length) == domain);
}

%(rulesBegin)s
%(rulesCode)s
%(rulesEnd)s

function inAutoProxy(a,l){var k=RULES[0];var g=RULES[1];var f=RULES[2];var b=RULES[3];var h,e,d,c;h=l.split(".");e=h.length;while(--e>=0){c=h[e];if(c in k&&k[c].constructor==Array){d=k[c].length;while(--d>=0){if(k[c][d].test(a)){return false}}}}d=f.length;while(--d>=0){if(f[d].test(a)){return false}}e=h.length;while(--e>=0){c=h[e];if(c in g&&g[c].constructor==Array){d=g[c].length;while(--d>=0){if(g[c][d].test(a)){return true}}}}d=b.length;while(--d>=0){if(b[d].test(a)){return true}}return false};'''

class jsRegExp:
    def __init__(self, r):
        self._r = r
    def __str__(self):
        return self._r

def dump(o):
    if isinstance(o, jsRegExp):
        return '/%s/i' % o
    elif isinstance(o, (list, tuple)):
        return '[%s]' % ', '.join((dump(i) for i in o))
    elif isinstance(o, dict):
        return '{%s}' % ', '.join(('%s: %s'%(dump(k),dump(v)) for k,v in o.iteritems()))
    elif isinstance(o, basestring):
        return '"%s"' % o.encode('string-escape')
    elif isinstance(o, (int, long)):
        return str(o)
    elif o is True: return 'true'
    elif o is False: return 'false'
    elif o is None: return 'null'
    else:
        raise TypeError(type(o))

def rule2js(ruleList):
    rules_hash =  [{}, {}]
    rules_share = [[], []]

    for line in ruleList.splitlines()[1:]:
        # Ignore the first line ([AutoProxy x.x]), empty lines and comments
        line = line.strip()
        if line and line[0] not in ('[','!','#'):
            rule_type = 1
            # Exceptions
            if line.startswith('@@'):
                line = line[2:]
                rule_type = 0
            # Regular expressions
            if line[0]=='/' and line[-1]=='/':
                line = line[1:-1]
            # Other cases
            else:
                # Strictly mapping to keyword blocking
                line = re.sub(r'^(https?:)', r'|\1', line)
                # Remove multiple wildcards
                line = re.sub(r'\*+', r'*', line)
                # Remove anchors following separator placeholder
                line = re.sub(r'\^\|$', r'^', line, 1)
                # Escape special symbols
                line = re.sub(r'(\W)', r'\\\1', line)
                # Replace wildcards by .*
                line = re.sub(r'\\\*', r'.*', line)
                # Process separator placeholders
                line = re.sub(r'\\\^', r'(?:[^\w\-.%\u0080-\uFFFF]|$)', line)
                # Process extended anchor at expression start
                line = re.sub(r'^\\\|\\\|', r'^[\w\-]+:\/+(?!\/)(?:[^\/]+\.)?', line, 1)
                # Process anchor at expression start
                line = re.sub(r'^\\\|', '^', line, 1)
                # Process anchor at expression end
                line = re.sub(r'\\\|$', '$', line, 1)
                # Remove leading wildcards
                line = re.sub(r'^(?:\.\*)', '', line, 1)
                # Remove trailing wildcards
                line = re.sub(r'(?:\.\*)$', '', line, 1)

            if line:
                idot = line.find('\\.')
                if idot == -1: hash_key = None
                else:
                    istart = line.find(':') + 1
                    if istart > idot: istart = 0
                    iend = line.find('\\/', idot+2)
                    if iend == -1:
                        iend = line.find('.*', idot+2)
                    helper = line[istart:iend if iend!=-1 else None].replace('\\-', '-')
                    helper = re.sub(r'(?:\(.*?\)\?)|(?:\(.*?\|.*?\))', '()', helper)
                    helper = re.sub(r'(?:[\w-]+)?(?:\.\*|\?|\|)(?:[\w-]+)?', '.*', helper)
                    helper = map(str.lower, re.findall(r'[\w-]{2,}', helper))
                    try:
                        hash_key = helper.pop()
                        if helper: hash_key = max(helper, key=lambda x:len(x))
                    except IndexError:
                        hash_key = None
                if hash_key:
                    if hash_key in rules_hash[rule_type]:
                        rules_hash[rule_type][hash_key].append(line)
                    else:
                        rules_hash[rule_type][hash_key] = [line]
                else:
                    rules_share[rule_type].append(line)
    # Remove repeated rules
    count = [0, 0]
    for i in (0, 1):
        for k,v in rules_hash[i].iteritems():
            rules_hash[i][k] = [jsRegExp(r) for r in set(v)]
            count[i] += len(rules_hash[i][k])
        rules_share[i] = [jsRegExp(r) for r in set(rules_share[i])]
    count = 'Rules Count: %d:%d %d:%d %d %d' % (len(rules_hash[0]), count[0],
            len(rules_hash[1]), count[1], len(rules_share[0]), len(rules_share[1]))
    print count+'\n'
    return rules_hash+rules_share, count

def initRuleList():
    ruleLists = []
    updateDates = []
    opener = urllib2.build_opener(urllib2.ProxyHandler(fetchProxy))
    print 'Fetching AutoProxy List:'
    for url in rulesUrl:
        print '  %s...' % url,
        try:
            resp = opener.open(url, timeout=20)
            ruleList = resp.read()
            updateDates.append(resp.info().getheader('last-modified'))
            resp.close()
        except:
            print 'Failed'
            continue
        try:
            tmp = ruleList.decode('base64')
            tmp[5:15].decode('ascii')
            ruleList = tmp
        except:
            pass
        print '%d bytes' % len(ruleList)
        ruleLists.append(ruleList)
    print 'Converting rules to PAC...'
    updateDates = '|'.join(updateDates)
    ruleLists, count = rule2js('\n'.join(ruleLists))
    if True not in map(bool, ruleLists):
        print 'Fetch failed.'
        sys.exit(-1)
    return  '// Last Update: %s\n// %s\nvar RULES=%s;' % \
            (updateDates, count, dump(ruleLists))

def parseTemplate(content):
    template, n = re.subn(r'(?ms)^(\s*%s\s*)^.*$(\s*%s\s*)$' % (re.escape(rulesBegin),
        re.escape(rulesEnd)), r'\1%(rulesCode)s\2', content)
    if n == 0:
        template = defaultPacTemplate
    return template

def main():
    try:
        f = open(pacFile, 'r')
        template = parseTemplate(f.read().replace('%','%%'))
        f.close()
    except IOError:
        template = defaultPacTemplate
    template = template % {'proxy': fetchProxy['http'] if fetchProxy else '127.0.0.1:8086',
        'rulesBegin': rulesBegin, 'rulesCode': initRuleList(), 'rulesEnd': rulesEnd}
    print 'Writing PAC to file...'
    f = open(pacFile, 'w')
    f.write(template)
    f.close()
    print 'Done!'

if __name__ == '__main__':
    main()